Een uitgebreide handleiding voor de TypeScript Compiler API, met Abstract Syntax Trees (AST), codeanalyse, transformatie en generatie voor internationale ontwikkelaars.
TypeScript Compiler API: AST-manipulatie en codetransformatie onder de knie krijgen
De TypeScript Compiler API biedt een krachtige interface voor het analyseren, manipuleren en genereren van TypeScript- en JavaScript-code. De kern ervan wordt gevormd door de Abstract Syntax Tree (AST), een gestructureerde weergave van uw broncode. Als u begrijpt hoe u met de AST kunt werken, ontgrendelt u mogelijkheden voor het bouwen van geavanceerde tooling, zoals linters, codeformatteerders, statische analyzers en aangepaste codegeneratoren.
Wat is de TypeScript Compiler API?
De TypeScript Compiler API is een verzameling TypeScript-interfaces en -functies die de innerlijke werking van de TypeScript-compiler blootleggen. Het stelt ontwikkelaars in staat om programmatisch te interageren met het compilatieproces, en gaat verder dan alleen het compileren van code. U kunt het gebruiken om:
- Code analyseren: Code structuur inspecteren, potentiële problemen identificeren en semantische informatie extraheren.
- Code transformeren: Bestaande code aanpassen, nieuwe functies toevoegen of code automatisch refactoren.
- Code genereren: Nieuwe code helemaal opnieuw maken op basis van sjablonen of andere input.
Deze API is essentieel voor het bouwen van geavanceerde ontwikkeltools die de codekwaliteit verbeteren, repetitieve taken automatiseren en de productiviteit van ontwikkelaars verhogen.
De Abstract Syntax Tree (AST) begrijpen
De AST is een boomachtige weergave van de structuur van uw code. Elk knooppunt in de boom vertegenwoordigt een syntactische constructie, zoals een variabeledeclaratie, een functieaanroep of een besturingsstroomstatement. De TypeScript Compiler API biedt tools om de AST te doorlopen, de knooppunten te inspecteren en ze aan te passen.
Neem deze eenvoudige TypeScript-code:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));
De AST voor deze code zou de functie declaratie, de return statement, de template literal, de console.log aanroep en andere elementen van de code weergeven. Het visualiseren van de AST kan een uitdaging zijn, maar tools zoals AST explorer (astexplorer.net) kunnen helpen. Met deze tools kunt u code invoeren en de bijbehorende AST in een gebruiksvriendelijke indeling bekijken. Het gebruik van AST Explorer helpt u de soort code structuur te begrijpen die u gaat manipuleren.
Belangrijkste AST-knooppunttypen
De TypeScript Compiler API definieert verschillende AST-knooppunttypen, die elk een andere syntactische constructie vertegenwoordigen. Hier zijn enkele veelvoorkomende knooppunttypen:
- SourceFile: Vertegenwoordigt een volledig TypeScript-bestand.
- FunctionDeclaration: Vertegenwoordigt een functiedefinitie.
- VariableDeclaration: Vertegenwoordigt een variabeledeclaratie.
- Identifier: Vertegenwoordigt een identifier (bijv. variabele naam, functie naam).
- StringLiteral: Vertegenwoordigt een string literal.
- CallExpression: Vertegenwoordigt een functieaanroep.
- ReturnStatement: Vertegenwoordigt een return statement.
Elk knooppunttype heeft eigenschappen die informatie geven over het bijbehorende code-element. Een `FunctionDeclaration`-knooppunt kan bijvoorbeeld eigenschappen hebben voor de naam, parameters, het retourtype en de body.
Aan de slag met de Compiler API
Om de Compiler API te kunnen gebruiken, moet u TypeScript installeren en een basiskennis van de TypeScript-syntaxis hebben. Hier is een eenvoudig voorbeeld dat laat zien hoe u een TypeScript-bestand kunt lezen en de AST ervan kunt afdrukken:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015, // Target ECMAScript version
true // SetParentNodes: true to retain parent references in the AST
);
function printAST(node: ts.Node, indent = 0) {
const indentStr = " ".repeat(indent);
console.log(`${indentStr}${ts.SyntaxKind[node.kind]}`);
node.forEachChild(child => printAST(child, indent + 1));
}
printAST(sourceFile);
Uitleg:
- Importeer modules: Importeert de `typescript`-module en de `fs`-module voor bestandsysteem bewerkingen.
- Lees bronbestand: Leest de inhoud van een TypeScript-bestand met de naam `example.ts`. U moet een `example.ts`-bestand maken om dit te laten werken.
- Maak bronbestand: Maakt een `SourceFile`-object, dat de root van de AST vertegenwoordigt. De functie `ts.createSourceFile` parsed de broncode en genereert de AST.
- Print AST: Definieert een recursieve functie `printAST` die de AST doorloopt en de aard van elk knooppunt afdrukt.
- Roep printAST aan: Roept `printAST` aan om te beginnen met het afdrukken van de AST vanuit het root `SourceFile`-knooppunt.
Om deze code uit te voeren, slaat u deze op als een `.ts`-bestand (bijv. `ast-example.ts`), maakt u een `example.ts`-bestand met wat TypeScript-code, en compileert en voert u vervolgens de code uit:
tsc ast-example.ts
node ast-example.js
Dit print de AST van uw `example.ts`-bestand naar de console. De uitvoer toont de hiërarchie van knooppunten en hun typen. Het kan bijvoorbeeld `FunctionDeclaration`, `Identifier`, `Block` en andere knooppunttypen weergeven.
De AST doorlopen
De Compiler API biedt verschillende manieren om de AST te doorlopen. De eenvoudigste is het gebruik van de `forEachChild`-methode, zoals weergegeven in het vorige voorbeeld. Deze methode bezoekt elk onderliggend knooppunt van een gegeven knooppunt.
Voor meer complexe traversal scenario's kunt u een `Visitor`-patroon gebruiken. Een visitor is een object dat methoden definieert die moeten worden aangeroepen voor specifieke knooppunttypen. Hierdoor kunt u het traversal proces aanpassen en acties uitvoeren op basis van het knooppunttype.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
class IdentifierVisitor {
visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
console.log(`Found identifier: ${node.text}`);
}
ts.forEachChild(node, n => this.visit(n));
}
}
const visitor = new IdentifierVisitor();
visitor.visit(sourceFile);
Uitleg:
- IdentifierVisitor Class: Definieert een klasse `IdentifierVisitor` met een `visit`-methode.
- Visit Method: De `visit`-methode controleert of het huidige knooppunt een `Identifier` is. Zo ja, dan wordt de tekst van de identifier afgedrukt. Vervolgens wordt `ts.forEachChild` recursief aangeroepen om de onderliggende knooppunten te bezoeken.
- Maak Visitor: Maakt een instantie van de `IdentifierVisitor`.
- Start Traversal: Roept de `visit`-methode aan op de `SourceFile` om de traversal te starten.
Dit voorbeeld laat zien hoe u alle identifiers in de AST kunt vinden. U kunt dit patroon aanpassen om andere knooppunttypen te vinden en verschillende acties uit te voeren.
De AST transformeren
De echte kracht van de Compiler API ligt in het vermogen om de AST te transformeren. U kunt de AST aanpassen om de structuur en het gedrag van uw code te wijzigen. Dit is de basis voor code refactoring tools, codegeneratoren en andere geavanceerde tooling.
Om de AST te transformeren, moet u de functie `ts.transform` gebruiken. Deze functie neemt een `SourceFile` en een lijst met `TransformerFactory`-functies. Een `TransformerFactory` is een functie die een `TransformationContext` aanneemt en een `Transformer`-functie retourneert. De `Transformer`-functie is verantwoordelijk voor het bezoeken en transformeren van knooppunten in de AST.
Hier is een eenvoudig voorbeeld dat laat zien hoe u een opmerking aan het begin van een TypeScript-bestand kunt toevoegen:
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
const transformerFactory: ts.TransformerFactory = context => {
return transformer => {
return node => {
if (ts.isSourceFile(node)) {
// Create a leading comment
const comment = ts.addSyntheticLeadingComment(
node,
ts.SyntaxKind.MultiLineCommentTrivia,
" This file was automatically transformed ",
true // hasTrailingNewLine
);
return node;
}
return node;
};
};
};
const { transformed } = ts.transform(sourceFile, [transformerFactory]);
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed
});
const result = printer.printFile(transformed[0]);
fs.writeFileSync("example.transformed.ts", result);
Uitleg:
- TransformerFactory: Definieert een `TransformerFactory`-functie die een `Transformer`-functie retourneert.
- Transformer: De `Transformer`-functie controleert of het huidige knooppunt een `SourceFile` is. Zo ja, dan wordt een voorloopopmerking aan het knooppunt toegevoegd met behulp van `ts.addSyntheticLeadingComment`.
- ts.transform: Roept `ts.transform` aan om de transformatie toe te passen op de `SourceFile`.
- Printer: Maakt een `Printer`-object om code te genereren uit de getransformeerde AST.
- Print en Schrijf: Print de getransformeerde code en schrijft deze naar een nieuw bestand met de naam `example.transformed.ts`.
Dit voorbeeld toont een eenvoudige transformatie, maar u kunt hetzelfde patroon gebruiken om complexere transformaties uit te voeren, zoals het refactoren van code, het toevoegen van logging statements of het genereren van documentatie.
Geavanceerde transformatietechnieken
Hier zijn enkele geavanceerde transformatietechnieken die u kunt gebruiken met de Compiler API:
- Nieuwe knooppunten maken: Gebruik de `ts.createXXX`-functies om nieuwe AST-knooppunten te maken. `ts.createVariableDeclaration` maakt bijvoorbeeld een nieuw variabeledeclaratieknooppunt.
- Knooppunten vervangen: Vervang bestaande knooppunten door nieuwe knooppunten met behulp van de functie `ts.visitEachChild`.
- Knooppunten toevoegen: Voeg nieuwe knooppunten toe aan de AST met behulp van de functies `ts.updateXXX`. `ts.updateBlock` werkt bijvoorbeeld een blokstatement bij met nieuwe statements.
- Knooppunten verwijderen: Verwijder knooppunten uit de AST door `undefined` te retourneren vanuit de transformerfunctie.
Code generatie
Na het transformeren van de AST moet u er code uit genereren. De Compiler API biedt hiervoor een `Printer`-object. De `Printer` neemt een AST en genereert een string representatie van de code.
De functie `ts.createPrinter` maakt een `Printer`-object. U kunt de printer configureren met verschillende opties, zoals het regeleinde teken dat moet worden gebruikt en of opmerkingen moeten worden weergegeven.
De methode `printer.printFile` neemt een `SourceFile` en retourneert een string representatie van de code. U kunt deze string vervolgens naar een bestand schrijven.
Praktische toepassingen van de Compiler API
De TypeScript Compiler API heeft tal van praktische toepassingen in software ontwikkeling. Hier zijn een paar voorbeelden:
- Linters: Bouw aangepaste linters om codeerstandaarden af te dwingen en potentiële problemen in uw code te identificeren.
- Codeformatteerders: Maak codeformatteerders om uw code automatisch te formatteren volgens een specifieke stijlgids.
- Statische analyzers: Ontwikkel statische analyzers om bugs, beveiligings kwetsbaarheden en prestatie knelpunten in uw code op te sporen.
- Codegeneratoren: Genereer code van sjablonen of andere invoer, automatiseer repetitieve taken en verminder boilerplate code. Genereer bijvoorbeeld API-clients of database schema's van een beschrijvingsbestand.
- Refactoring tools: Bouw refactoring tools om automatisch variabelen te hernoemen, functies te extraheren of code tussen bestanden te verplaatsen.
- Internationalisatie (i18n) automatisering: Extraheer automatisch vertaalbare strings uit uw TypeScript-code en genereer lokalisatiebestanden voor verschillende talen. Een tool kan bijvoorbeeld code scannen op strings die worden doorgegeven aan een `translate()` functie en deze automatisch toevoegen aan een vertaalbron bestand.
Voorbeeld: Een eenvoudige linter bouwen
Laten we een eenvoudige linter maken die controleert op ongebruikte variabelen in TypeScript-code. Deze linter identificeert variabelen die zijn gedeclareerd maar nooit worden gebruikt.
import * as ts from "typescript";
import * as fs from "fs";
const fileName = "example.ts";
const sourceCode = fs.readFileSync(fileName, "utf8");
const sourceFile = ts.createSourceFile(
fileName,
sourceCode,
ts.ScriptTarget.ES2015,
true
);
function findUnusedVariables(sourceFile: ts.SourceFile) {
const usedVariables = new Set();
function visit(node: ts.Node) {
if (ts.isIdentifier(node)) {
usedVariables.add(node.text);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
const unusedVariables: string[] = [];
function checkVariableDeclaration(node: ts.Node) {
if (ts.isVariableDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
const variableName = node.name.text;
if (!usedVariables.has(variableName)) {
unusedVariables.push(variableName);
}
}
ts.forEachChild(node, checkVariableDeclaration);
}
checkVariableDeclaration(sourceFile);
return unusedVariables;
}
const unusedVariables = findUnusedVariables(sourceFile);
if (unusedVariables.length > 0) {
console.log("Unused variables:");
unusedVariables.forEach(variable => console.log(`- ${variable}`));
} else {
console.log("No unused variables found.");
}
Uitleg:
- findUnusedVariables Function: Definieert een functie `findUnusedVariables` die een `SourceFile` als invoer neemt.
- usedVariables Set: Maakt een `Set` om de namen van gebruikte variabelen op te slaan.
- visit Function: Definieert een recursieve functie `visit` die de AST doorloopt en de namen van alle identifiers toevoegt aan de `usedVariables` set.
- checkVariableDeclaration Function: Definieert een recursieve functie `checkVariableDeclaration` die controleert of een variabeledeclaratie ongebruikt is. Zo ja, dan wordt de variabele naam toegevoegd aan de array `unusedVariables`.
- Return unusedVariables: Retourneert een array met de namen van alle ongebruikte variabelen.
- Output: Print de ongebruikte variabelen naar de console.
Dit voorbeeld toont een eenvoudige linter. U kunt het uitbreiden om te controleren op andere codeerstandaarden en andere potentiële problemen in uw code te identificeren. U kunt bijvoorbeeld controleren op ongebruikte imports, te complexe functies of potentiële beveiligings kwetsbaarheden. De sleutel is om te begrijpen hoe u de AST kunt doorlopen en de specifieke knooppunttypen kunt identificeren waarin u bent geïnteresseerd.
Best practices en overwegingen
- Begrijp de AST: Investeer tijd in het begrijpen van de structuur van de AST. Gebruik tools zoals AST explorer om de AST van uw code te visualiseren.
- Gebruik Type Guards: Gebruik type guards (`ts.isXXX`) om ervoor te zorgen dat u met de juiste knooppunttypen werkt.
- Houd rekening met de prestaties: AST-transformaties kunnen rekenkundig duur zijn. Optimaliseer uw code om het aantal knooppunten dat u bezoekt en transformeert te minimaliseren.
- Fouten afhandelen: Verwerk fouten op een elegante manier. De Compiler API kan uitzonderingen genereren als u ongeldige bewerkingen op de AST probeert uit te voeren.
- Test grondig: Test uw transformaties grondig om ervoor te zorgen dat ze de gewenste resultaten opleveren en geen nieuwe bugs introduceren.
- Gebruik bestaande bibliotheken: Overweeg het gebruik van bestaande bibliotheken die abstracties op een hoger niveau bieden over de Compiler API. Deze bibliotheken kunnen veelvoorkomende taken vereenvoudigen en de hoeveelheid code die u moet schrijven verminderen. Voorbeelden zijn `ts-morph` en `typescript-eslint`.
Conclusie
De TypeScript Compiler API is een krachtige tool voor het bouwen van geavanceerde ontwikkeltools. Door te begrijpen hoe u met de AST kunt werken, kunt u linters, codeformatteerders, statische analyzers en andere tools maken die de codekwaliteit verbeteren, repetitieve taken automatiseren en de productiviteit van ontwikkelaars verhogen. Hoewel de API complex kan zijn, zijn de voordelen van het beheersen ervan aanzienlijk. Deze uitgebreide handleiding biedt een basis voor het effectief verkennen en gebruiken van de Compiler API in uw projecten. Vergeet niet om tools zoals AST Explorer te gebruiken, knooppunttypen zorgvuldig af te handelen en uw transformaties grondig te testen. Met oefening en toewijding kunt u het volledige potentieel van de TypeScript Compiler API ontsluiten en innovatieve oplossingen bouwen voor het software ontwikkelings landschap.
Verdere verkenning:
- TypeScript Compiler API-documentatie: [https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API)
- AST Explorer: [https://astexplorer.net/](https://astexplorer.net/)
- ts-morph library: [https://ts-morph.com/](https://ts-morph.com/)
- typescript-eslint: [https://typescript-eslint.io/](https://typescript-eslint.io/)